package top.yaofengqiao.springcloudsimple.web.starter.config;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.condition.PathPatternsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import top.yaofengqiao.springcloudsimple.web.starter.security.MemoryPermissionHandler;
import top.yaofengqiao.springcloudsimple.web.starter.security.PermissionHandler;
import top.yaofengqiao.springcloudsimple.web.starter.security.TokenCache;
import top.yaofengqiao.springcloudsimple.web.starter.security.filter.TokenFilter;
import top.yaofengqiao.springcloudsimple.web.starter.security.handler.Oauth2DeniedHandler;
import top.yaofengqiao.springcloudsimple.web.starter.security.handler.Oauth2UnauthorizedHandler;

import javax.annotation.Resource;
import javax.annotation.security.PermitAll;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @author yfq
 * @date 2024/6/17 15:15
 * @description
 */
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SimpleSecurityAutoConfiguration {
    @Resource
    private ApplicationContext applicationContext;

    /**
     * 未认证处理器
     */
    @Bean
    public AccessDeniedHandler oauth2DeniedHandler() {
        return new Oauth2DeniedHandler();
    }

    /**
     * 未授权处理器
     */
    @Bean
    public AuthenticationEntryPoint oauth2UnauthorizedHandler() {
        return new Oauth2UnauthorizedHandler();
    }

    /**
     * token处理器
     */
    @Bean
    public TokenFilter tokenFilter() {
        return new TokenFilter();
    }

    @Bean
    public TokenCache tokenCache() {
        return new TokenCache();
    }

    /**
     * 权限处理器
     */
    @Bean
    public PermissionHandler mp() {
        return new MemoryPermissionHandler();
    }

    /**
     * 配置密码加密器
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 认证管理器
     */
    @Bean
    public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
        return http.getSharedObject(AuthenticationManagerBuilder.class).build();
    }

    /**
     * 过滤器链
     */
    @Bean
    @Primary
    public SecurityFilterChain managementSecurityFilterChain(HttpSecurity http, TokenFilter tokenFilter) throws Exception {
        // 1. 基本配置
        http
                // 允许跨域
                .cors()
                .and()
                // 关闭csrf
                .csrf().disable()
                // 禁止表单登录
                .formLogin().disable()
                // 不通过Session获取SecurityContext
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        // 2. url权限配置
        http
                .authorizeRequests()
                // 静态资源，可匿名访问(swagger文档)
                .antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll()
                // 白名单接口直接放行
                .antMatchers(obtainPermitUri().toArray(new String[0])).permitAll()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated();

        // 3. 异常处理配置
        http
                .exceptionHandling()
                // 未认证处理
                .authenticationEntryPoint(oauth2UnauthorizedHandler())
                // 未授权处理
                .accessDeniedHandler(oauth2DeniedHandler());

        // 4. 将自定义tokenFilter添加到过滤器链上
        http.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }

    private List<String> obtainPermitUri() {
        List<String> permitUriList = new ArrayList<>();

        // 请求处理器映射器(通过它可以拿到项目中所有的url映射信息)
        RequestMappingHandlerMapping mapping = (RequestMappingHandlerMapping) applicationContext.getBean("requestMappingHandlerMapping");
        Map<RequestMappingInfo, HandlerMethod> handlerMethods = mapping.getHandlerMethods();

        for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethods.entrySet()) {
            RequestMappingInfo key = entry.getKey();
            HandlerMethod value = entry.getValue();
            PathPatternsRequestCondition pathPatternsCondition = key.getPathPatternsCondition();

            if (pathPatternsCondition == null) continue;

            // 包含了PermitAll注解就加到白名单
            if (value.getMethod().isAnnotationPresent(PermitAll.class))
                permitUriList.addAll(pathPatternsCondition.getPatternValues());
        }

        // prometheus监控路径
        permitUriList.add("/actuator/**");

        // swagger配置地址
        permitUriList.add("/v3/api-docs/**");
        return permitUriList;
    }
}
